En aquesta segona part de la pràctica, el meu objectiu és anar més enllà d’un dashboard i construir una visualització que:
El conjunt de dades prové del Healthy Brain Network
(HBN) (OpenNeuro) i en aquesta entrega treballo amb el fitxer
participants.csv. (A la Part I vaig justificar la
rellevància del dataset i les preguntes de recerca.)
Preguntes que vull respondre (Part I): diferències per sexe, efecte de l’edat, relació MFQ–SHAPS, diferències per grup clínic, i possible paper de la lateralitat manual. En aquest projecte em plantejo cinc preguntes principals: (1) si existeixen diferències en els nivells de MFQ i SHAPS segons el sexe, (2) com varia el malestar emocional amb l’edat, (3) quina relació existeix entre MFQ i SHAPS, (4) si els grups clínics mostren patrons diferenciats de malestar, i (5) si la lateralitat manual s’associa a diferències emocionals.
path <- "/Users/natalialopezlopezicloud.com/Desktop/UOC\ /participants.csv"
raw <- readr::read_csv(path, show_col_types = FALSE) %>%
clean_names()
glimpse(raw)
## Rows: 51
## Columns: 7
## $ participant_id <chr> "sub-20900", "sub-22686", "sub-23017", "sub-23108", "su…
## $ sex <chr> "M", "M", "F", "F", "F", "F", "F", "F", "F", "M", "F", …
## $ age <dbl> 17, 16, 17, 16, 16, 17, 17, 16, 17, 16, 16, 15, 15, 17,…
## $ handedness <chr> "RIGHT", "RIGHT", "RIGHT", "RIGHT", "RIGHT", "RIGHT", "…
## $ group <chr> "HV", "HV", "MDD", "HV", "HV", "MDD", "MDD", "MDD", "MD…
## $ mfq <chr> "0.0", "2.0", "15.0", "0.0", "0.0", "1.0", "7.0", "5.0"…
## $ shaps <chr> "1.0", "10.0", "6.0", "7.0", "0.0", "14.0", "22.0", "21…
# En aquesta secció faig una neteja mínima però transparent:
# - estandarditzo nivells categòrics,
# - converteixo edat a numèrica,
# - gestiono missings (els mantinc, però els controlo per visualització).
df <- raw %>%
mutate(
sex = as.factor(sex),
handedness = as.factor(handedness),
group = as.factor(group),
age = as.numeric(age),
mfq = as.numeric(mfq),
shaps = as.numeric(shaps)
)
# Resum de missings per variable
missings <- df %>%
summarise(across(everything(), ~ sum(is.na(.)))) %>%
pivot_longer(everything(), names_to = "variable", values_to = "n_missing") %>%
arrange(desc(n_missing))
missings
## # A tibble: 7 × 2
## variable n_missing
## <chr> <int>
## 1 mfq 5
## 2 shaps 5
## 3 participant_id 0
## 4 sex 0
## 5 age 0
## 6 handedness 0
## 7 group 0
En comptes de limitar-me a les variables originals, creo variables noves per donar més valor analític i per facilitar una exploració més rica:
df2 <- df %>%
mutate(
age_group = case_when(
is.na(age) ~ NA_character_,
age < 10 ~ "Menys de 10",
age >= 10 & age < 13 ~ "10–12",
age >= 13 & age < 16 ~ "13–15",
age >= 16 & age < 19 ~ "16–18",
age >= 19 ~ "19 o més"
) %>% factor(levels = c("Menys de 10", "10–12", "13–15", "16–18", "19 o més")),
# Estandardització: em permet comparar MFQ i SHAPS en una escala comuna
mfq_z = as.numeric(scale(mfq)),
shaps_z = as.numeric(scale(shaps)),
# Índex compostde malestar:
# faig la mitjana dels z-scores; així el valor 0 ≈ mitjana mostra
distress_index = rowMeans(cbind(mfq_z, shaps_z), na.rm = FALSE),
# Categories de risc per quantils
distress_q = ntile(distress_index, 4),
distress_level = factor(
distress_q,
levels = 1:4,
labels = c("Baix", "Mitjà-baix", "Mitjà-alt", "Alt")
)
)
# Taula ràpida per veure les noves variables
df2 %>%
select(participant_id, sex, age, age_group, handedness, group, mfq, shaps,
mfq_z, shaps_z, distress_index, distress_level) %>%
head(10)
## # A tibble: 10 × 12
## participant_id sex age age_group handedness group mfq shaps mfq_z
## <chr> <fct> <dbl> <fct> <fct> <fct> <dbl> <dbl> <dbl>
## 1 sub-20900 M 17 16–18 RIGHT HV 0 1 -1.04
## 2 sub-22686 M 16 16–18 RIGHT HV 2 10 -0.678
## 3 sub-23017 F 17 16–18 RIGHT MDD 15 6 1.68
## 4 sub-23108 F 16 16–18 RIGHT HV 0 7 -1.04
## 5 sub-23303 F 16 16–18 RIGHT HV 0 0 -1.04
## 6 sub-23399 F 17 16–18 RIGHT MDD 1 14 -0.860
## 7 sub-23520 F 17 16–18 RIGHT MDD 7 22 0.229
## 8 sub-23540 F 16 16–18 RIGHT MDD 5 21 -0.134
## 9 sub-23546 F 17 16–18 RIGHT MDD 1 0 -0.860
## 10 sub-23549 M 16 16–18 RIGHT MDD 4 20 -0.316
## # ℹ 3 more variables: shaps_z <dbl>, distress_index <dbl>, distress_level <fct>
Decisió de disseny: he triat quantils per a
distress_level perquè és robust quan no tinc llindars
clínics oficials dins el fitxer i em permet explicar diferències
relatives dins la mostra.
# Resums descriptius
summary_num <- df2 %>%
summarise(
n = n(),
age_min = min(age, na.rm = TRUE),
age_med = median(age, na.rm = TRUE),
age_max = max(age, na.rm = TRUE),
mfq_med = median(mfq, na.rm = TRUE),
shaps_med = median(shaps, na.rm = TRUE)
)
summary_num
## # A tibble: 1 × 6
## n age_min age_med age_max mfq_med shaps_med
## <int> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 51 12 16 18 5 8.5
# Distribucions categòriques
tab_sex <- df2 %>% count(sex) %>% mutate(p = n / sum(n))
tab_group <- df2 %>% count(group) %>% mutate(p = n / sum(n))
tab_hand <- df2 %>% count(handedness) %>% mutate(p = n / sum(n))
tab_sex
## # A tibble: 2 × 3
## sex n p
## <fct> <int> <dbl>
## 1 F 40 0.784
## 2 M 11 0.216
tab_group
## # A tibble: 2 × 3
## group n p
## <fct> <int> <dbl>
## 1 HV 20 0.392
## 2 MDD 31 0.608
tab_hand
## # A tibble: 3 × 3
## handedness n p
## <fct> <int> <dbl>
## 1 AMBIDEXTROUS 3 0.0588
## 2 LEFT 4 0.0784
## 3 RIGHT 44 0.863
En comptes de mostrar 10 gràfics semblants, estructuro la història en
5 preguntes.
Cada pregunta té: 1 visual principal, i si cal
1 visual secundari (per no repetir).
Decisió de disseny: faig servir violin +
boxplot perquè vull veure distribució i mediana.
Afegeixo interactivitat amb Plotly per poder llegir valors i n sense
omplir la pantalla de text.
d_q1 <- df2 %>% filter(!is.na(sex), !is.na(mfq), !is.na(shaps))
p1 <- ggplot(d_q1, aes(x = sex, y = mfq, fill = sex)) +
geom_violin(trim = FALSE, alpha = 0.6) +
geom_boxplot(width = 0.15, outlier.alpha = 0.4) +
scale_fill_viridis_d(option = "C", end = 0.9) +
labs(
title = "MFQ per sexe (distribució + mediana)",
x = "Sexe",
y = "MFQ"
) +
theme_minimal() +
theme(legend.position = "none")
ggplotly(p1, tooltip = c("x", "y"))
Aquest gràfic mostra la distribució de les puntuacions del qüestionari MFQ segons el sexe. Cada violí representa com es distribueixen les puntuacions en dones (F) i homes (M), mentre que la línia central indica la mediana. S’observa que les dones presenten una mediana lleugerament més alta que els homes, cosa que suggereix que, en aquesta mostra, tendeixen a reportar més símptomes depressius. A més, la distribució de les dones és una mica més ampla, especialment cap a valors elevats, indicant més variabilitat en les puntuacions. Tot i això, hi ha un solapament important entre ambdós grups, fet que indica que molts participants de tots dos sexes tenen puntuacions similars. Per tant, aquestes diferències s’han d’interpretar de manera descriptiva i exploratòria, i no impliquen causalitat ni diagnòstic clínic.
p1b <- ggplot(d_q1, aes(x = sex, y = shaps, fill = sex)) +
geom_violin(trim = FALSE, alpha = 0.6) +
geom_boxplot(width = 0.15, outlier.alpha = 0.4) +
scale_fill_viridis_d(option = "C", end = 0.9) +
labs(
title = "SHAPS per sexe (distribució + mediana)",
x = "Sexe",
y = "SHAPS"
) +
theme_minimal() +
theme(legend.position = "none")
ggplotly(p1b, tooltip = c("x", "y"))
Aquest gràfic mostra la distribució de les puntuacions del qüestionari SHAPS segons el sexe. Cada violí representa la distribució completa de les puntuacions en dones (F) i homes (M), mentre que la línia central indica la mediana. S’observa que els homes presenten una mediana de SHAPS més alta que les dones, cosa que suggereix un nivell més elevat d’anhedonia en aquest grup. A més, la distribució dels homes sembla desplaçada cap a valors més alts, amb una concentració important de puntuacions entre valors mitjans i elevats. En canvi, en les dones la distribució és més ampla i heterogènia, amb presència tant de puntuacions baixes com de valors més elevats. Tot i aquestes diferències, hi ha un solapament notable entre ambdós sexes, fet que indica que no hi ha una separació clara entre els grups.
Decisió de disseny: per edat prefereixo: - una visió contínua (scatter + smooth) per captar tendència, - i una visió per grups (age_group) per facilitar interpretació a públic quye no sigui expert
d_q2 <- df2 %>% filter(!is.na(age), !is.na(distress_index))
p2 <- ggplot(d_q2, aes(x = age, y = distress_index)) +
geom_point(alpha = 0.35) +
geom_smooth(method = "loess", se = TRUE) +
labs(
title = "Índex de malestar (MFQ+SHAPS) en funció de l’edat",
x = "Edat",
y = "Índex de malestar (z mitjà)"
) +
theme_minimal()
ggplotly(p2, tooltip = c("x", "y"))
Aquest gràfic mostra la relació entre l’edat i l’índex de malestar emocional, que combina les puntuacions estandarditzades de MFQ i SHAPS. Cada punt representa un participant, mentre que la línia blava indica la tendència general i la banda grisa el seu interval d’incertesa. En general, no s’observa una relació lineal clara entre l’edat i el malestar emocional. La tendència es manté força estable al voltant del valor zero, que correspon a la mitjana de la mostra. S’intueix un lleuger augment del malestar cap a mitja adolescència (al voltant dels 14–15 anys), seguit d’una petita disminució en edats més avançades. Tot i això, la variabilitat individual és elevada a totes les edats, tal com indiquen la dispersió dels punts i l’amplada de la banda grisa. Això suggereix que l’edat, per si sola, no explica les diferències de malestar emocional dins d’aquesta mostra.
d_q2b <- df2 %>% filter(!is.na(age_group), !is.na(distress_index))
p2b <- ggplot(d_q2b, aes(x = age_group, y = distress_index, fill = age_group)) +
geom_boxplot(outlier.alpha = 0.35) +
scale_fill_viridis_d(option = "D", end = 0.9) +
labs(
title = "Distribució de l’índex de malestar per grups d’edat",
x = "Grup d’edat",
y = "Índex de malestar (z mitjà)"
) +
theme_minimal() +
theme(legend.position = "none")
ggplotly(p2b, tooltip = c("x", "y"))
Decisió de disseny: faig un scatter amb línia de tendència i color per sexe (si hi ha prou dades), perquè així veig: - relació general MFQ–SHAPS, - i si aquesta relació canvia per subgrups.
# 1. Dades netes
d_q3 <- df2 %>%
filter(!is.na(mfq), !is.na(shaps), !is.na(sex))
# 2. Correlació (Pearson)
cor_test <- cor.test(d_q3$mfq, d_q3$shaps, method = "pearson")
cor_test
##
## Pearson's product-moment correlation
##
## data: d_q3$mfq and d_q3$shaps
## t = 3.369, df = 43, p-value = 0.001601
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.1887684 0.6617484
## sample estimates:
## cor
## 0.456981
# 3. Gràfic: scatter + línia de tendència per sexe
p3 <- ggplot(
d_q3,
aes(x = mfq, y = shaps, color = sex, text = participant_id)
) +
geom_point(alpha = 0.55, size = 2) +
geom_smooth(
aes(group = sex),
method = "lm",
se = TRUE,
linewidth = 1
) +
scale_color_viridis_d(
option = "B",
end = 0.9,
na.value = "grey60"
) +
labs(
title = "Relació MFQ–SHAPS (amb tendència lineal per sexe)",
x = "MFQ (símptomes depressius)",
y = "SHAPS (anhedonia)",
color = "Sexe"
) +
theme_minimal(base_size = 13)
# 4. Versió interactiva
ggplotly(p3, tooltip = c("text", "x", "y", "color"))
Decisió de disseny: aquí em centro en una sola
mètrica (distress_index) per no duplicar gràfics.
Si group té molts nivells, l’interactiu ajuda a explorar
sense saturar la pantalla.
d_q4 <- df2 %>% filter(!is.na(group), !is.na(distress_index))
p4 <- ggplot(d_q4, aes(x = reorder(group, distress_index, median, na.rm = TRUE), y = distress_index)) +
geom_boxplot(outlier.alpha = 0.25) +
coord_flip() +
labs(
title = "Índex de malestar per grup clínic (ordenat per mediana)",
x = "Grup",
y = "Índex de malestar (z mitjà)"
) +
theme_minimal()
ggplotly(p4, tooltip = c("x", "y"))
Aquest gràfic mostra la distribució de l’índex de malestar emocional (basat en MFQ i SHAPS) segons el grup clínic, ordenat per la mediana. Les caixes representen la mediana i el rang interquartílic de cada grup. S’observa que el grup MDD presenta una mediana clarament més alta de l’índex de malestar en comparació amb el grup HV. Això indica que, de mitjana, els participants amb diagnòstic de depressió major mostren nivells més elevats de malestar emocional. A més, el grup MDD mostra una variabilitat més gran, amb un rang de valors que s’estén tant cap a puntuacions molt elevades com cap a valors més baixos. En canvi, el grup HV presenta una distribució més concentrada i amb valors majoritàriament per sota de la mitjana de la mostra (valors negatius). —
Decisió de disseny: en lloc d’un altre boxplot, faig
una visualització més “comparativa”: - percentatges de
distress_level per handedness (composició). Això aporta
varietat i és fàcil d’explicar.
d_q5 <- df2 %>%
filter(!is.na(handedness), !is.na(distress_level)) %>%
count(handedness, distress_level) %>%
group_by(handedness) %>%
mutate(p = n / sum(n)) %>%
ungroup()
p5 <- ggplot(d_q5, aes(x = handedness, y = p, fill = distress_level)) +
geom_col(position = "fill") +
scale_y_continuous(labels = percent) +
scale_fill_viridis_d(option = "A", end = 0.95) +
labs(
title = "Composició del nivell de malestar per lateralitat manual",
x = "Lateralitat manual",
y = "Proporció",
fill = "Nivell"
) +
theme_minimal()
ggplotly(p5, tooltip = c("x", "fill", "y"))
Aquest gràfic mostra la proporció de participants en cada nivell de malestar emocional segons la lateralitat manual. Cada barra representa el 100% del grup corresponent i es divideix en funció dels nivells de malestar (baix, mitjà-baix, mitjà-alt i alt). S’observa que el grup de persones esquerranes presenta majoritàriament nivells baixos de malestar, mentre que en el grup de persones dretanes la distribució és més heterogènia, amb presència de tots els nivells. En el grup ambidextre es detecta una proporció relativament més alta de nivells mitjà-alt i alt, tot i que aquest resultat s’ha d’interpretar amb prudència a causa del nombre reduït de participants. —
En aquesta part vull donar una eina real d’exploració: una taula
interactiva on puc filtrar per sexe, edat, grup, etc.
Això és útil per a públics més tècnics i també per validar ràpidament
casos.
tab <- df2 %>%
select(participant_id, sex, age, age_group, handedness, group, mfq, shaps, distress_index, distress_level) %>%
arrange(desc(distress_index))
DT::datatable(
tab,
filter = "top",
options = list(pageLength = 10, scrollX = TRUE),
rownames = FALSE
)
Aquesta taula interactiva mostra les dades individuals de tots els participants de l’estudi. Cada fila correspon a un participant i cada columna a una de les variables analitzades, com el sexe, l’edat, la lateralitat manual, el grup clínic i les puntuacions en MFQ i SHAPS. La interactivitat permet filtrar i ordenar les dades segons diferents criteris, facilitant l’exploració detallada dels casos individuals. Aquesta taula no té com a objectiu extreure conclusions directes, sinó complementar les visualitzacions i garantir la transparència de l’anàlisi. —
Aquí resumeixo els patrons que veig